#include "types.h"
#include "stat.h"
#include "user.h"

/* Possible states of a thread; */
#define FREE        0x0
#define RUNNING     0x1
#define RUNNABLE    0x2

#define STACK_SIZE  8192
#define MAX_THREAD  4

typedef struct thread thread_t, *thread_p;
typedef struct mutex mutex_t, *mutex_p;

struct thread {
	int        sp;                /* curent stack pointer */
    	char stack[STACK_SIZE];       /* the thread's stack */
	int        state;             /* running, runnable, waiting */
};
static thread_t all_thread[MAX_THREAD];
thread_p  current_thread;
thread_p  next_thread;
extern void thread_switch(void);

void 
thread_init(void)
{
	// main() is thread 0, which will make the first invocation to
	// thread_schedule().  it needs a stack so that the first thread_switch() can
	// save thread 0's state.  thread_schedule() won't run the main thread ever
	// again, because it is state is set to RUNNING, and thread_schedule() selects
	// a RUNNABLE thread.
	current_thread = &all_thread[0];
	current_thread->state = RUNNING;
}

static void 
thread_schedule(void)
{
	thread_p t;

	/* Find another runnable thread. */
	for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
  		if (t->state == RUNNABLE && t != current_thread) {
			next_thread = t;
			break;
		}
	}

	if (t >= all_thread + MAX_THREAD && current_thread->state == RUNNABLE) {
		/* The current thread is the only runnable thread; run it. */
		next_thread = current_thread;
	}

	if (next_thread == 0) {
		printf(2, "thread_schedule: no runnable threads; deadlock\n");
		exit();
	}

	if (current_thread != next_thread) {         /* switch threads?  */
		next_thread->state = RUNNING;
		thread_switch();

	} else
		next_thread = 0;
}

void 
thread_create(void (*func)())
{
	thread_p t;
    
	for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
		if (t->state == FREE) break;
	}
	t->sp = (int) (t->stack + STACK_SIZE);   // set sp to the top of the stack
	t->sp -= 4;                              // space for return address
	* (int *) (t->sp) = (int)func;           // push return address on stack
	t->sp -= 32;                             // space for registers that thread_switch will push
	t->state = RUNNABLE;
}

void 
thread_yield(void)
{
	current_thread->state = RUNNABLE;
    	thread_schedule();
}

static void 
mythread(void)
{
	int i;
	printf(1, "my thread running\n");
	for (i = 0; i < 100; i++) {
		printf(1, "my thread 0x%x\n", (int) current_thread);
		thread_yield();
	}
	printf(1, "my thread: exit\n");
	current_thread->state = FREE;
	thread_schedule();
}


int 
main(int argc, char *argv[]) 
{
	thread_init();
	thread_create(mythread);
	thread_create(mythread);
	thread_schedule();
	return 0;
}
